package com.katze.common.zip;

import org.apache.commons.compress.archivers.zip.ParallelScatterZipCreator;
import org.apache.commons.compress.archivers.zip.ScatterZipOutputStream;
import org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream;

import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collection;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;

/***
 *  说  明：
 *  作  者：Admin
 *  日  期：2019/1/8 15:01
 ***/
public class ScatterSample {
    private final ParallelScatterZipCreator parallelScatter;

    // ParallelScatterZipCreator api says:
    // 注意这个类不保证写入到输出文件的顺序。需要保持特定顺序的（manifests，文件夹）必须使用这个类的客户类进行处理
    // 通常的做法是 在调用这个类的writeTo方法前把这些东西写入到ZipArchiveOutputStream
    private final ScatterZipOutputStream directoryScatter;

    //压缩文件中携带目录时是否从根目录开始压缩  (仅对压缩本地文件有效)
    private final boolean isRoot;

    public ScatterSample(boolean isRoot) {
        try {
            this.isRoot = isRoot;
            this.parallelScatter = new ParallelScatterZipCreator();
            this.directoryScatter = ScatterZipOutputStream.fileBased(File.createTempFile("zip-scatter-dirs", "tmp"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static ScatterSample builder() {
        return new ScatterSample(false);
    }

    public static ScatterSample builder(boolean isRoot) {
        return new ScatterSample(isRoot);
    }

    /**
     * 遍历压缩文件
     * @param targets 压缩文件
     * @param archive 压缩非目录时回调接口
     */
    public <T>ScatterSample foreach(Collection<T> targets, Function<T, ZipArchiveRequest> archive) throws IOException {
        for (T target : targets) {
            if (target instanceof Path && Files.isDirectory((Path) target)) {
                int start = ((Path) target).getNameCount() - (isRoot? 1:0);
                Files.walkFileTree((Path) target, new SimpleFileVisitor<>() {
                    @Override
                    public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
                        addEntry(new ZipArchiveRequest(file, file.subpath(start, file.getNameCount())));
                        return FileVisitResult.CONTINUE;
                    }
                });
            } else {
                addEntry(archive.apply(target));
            }
        }
        return this;
    }

    public void writeTo(Path storage) throws IOException, InterruptedException, ExecutionException {
        try (ZipArchiveOutputStream outputStream = new ZipArchiveOutputStream(Files.newOutputStream(storage))){
            try {
                directoryScatter.writeTo(outputStream);
            } finally {
                directoryScatter.close();
            }
            parallelScatter.writeTo(outputStream);
        }
    }

    private void addEntry(ZipArchiveRequest archive) throws IOException {
        if (archive.isDirectory() && !archive.isUnixSymlink()) {
            directoryScatter.addArchiveEntry(archive.get());
        } else {
            parallelScatter.addArchiveEntry(archive);
        }
    }
}
